function Observer(data) {
  // 存储data数据,后面要使用
  this.data = data
  // 继续往下走
  this.walk(data)
}

Observer.prototype = {
  walk: function (data) {
    // 又将Observer实例存储起来
    var me = this
    // 把data对象中所有的属性收集起来存放到一个数组中并遍历
    Object.keys(data).forEach(function (key) {
      // 要将data中的每一条数据变成响应式数据
      me.defineReactive(data, key, data[key])
    })
  },

  defineReactive: function (data, key, val) {
    // 这个dep实例,未来用于让当前数据和使用当前数据的模板进行绑定(怎么绑定的暂时还不知道)
    var dep = new Dep()
    // 马上就要对data进行数据劫持了,但是在此之前有递归调用了obeserve,是因为有可能data中的某个数据也是对象,也要进行数据劫持. (整个劫持过程,是先劫持里面的对象,最后劫持data)
    var childObj = observe(val)
    // 数据劫持(监听data数据被获取或被赋值)
    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: false,

      get: function () {
        // 5. 此时Dep.target已经是解析模板时曾经的Watcher实例了
        if (Dep.target) {
          // 6. 继续往下走,让watcher和dep互相存储
          dep.depend()
        }
        return val
      },
      set: function (newVal) {
        // 14.当msg的值被修改的时候,这个set触发
        // 判断新的值和旧的值是否发生变化,不变则则什么都不做
        if (newVal === val) {
          return
        }
        // 赋值给val,存储一下新的值,未来这个新的值就变成旧值
        val = newVal
        // 由于新的值可能是一个对象,如果是对象,就要继续对这个对象的数据进行数据劫持,所以这里又调用了一次observe
        childObj = observe(newVal)
        // 15.数据已经被修改了,这时触发dep实例的notify函数,准备找到对应的模板然后将最新的值渲染到模板中
        dep.notify()
      },
    })
  },
}

function observe(value, vm) {
  //判断当前传入的数据是否是对象,如果是对象才进行数据劫持(注意: 这个判断是给data中的数据的数据使用的)
  if (!value || typeof value !== 'object') {
    return
  }
  // 开始数据劫持了
  return new Observer(value)
}

var uid = 0

function Dep() {
  this.id = uid++
  this.subs = []
}

Dep.prototype = {
  addSub: function (sub) {
    this.subs.push(sub)
  },

  depend: function () {
    // 7. Dep.target是Watcher实例,此时调用addDep这个函数,就是要让watcher存储dep,dep存储watcher
    Dep.target.addDep(this)
  },

  removeSub: function (sub) {
    var index = this.subs.indexOf(sub)
    if (index != -1) {
      this.subs.splice(index, 1)
    }
  },

  notify: function () {
    // 16. 遍历dep里面存储的所有的watcher实例,然后调用watcher实例的update方法
    this.subs.forEach(function (sub) {
      sub.update()
    })
  },
}

Dep.target = null
